package moldat;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.SQLException;
import java.util.Properties;
import javax.servlet.http.HttpServlet;
import moldat.utilities.GeneNameParser;
import moldat.utilities.HTTPStringDownloader;

/**
 * Diese Klasse speichert und verwaltet alle relevanten Einstellungen. Die
 * Einstellungen werden in der Datei 'MolDat.properties' gespeichert. Falls
 * diese Datei noch nicht existiert wird eine Default-Datei erstellt. Es werden
 * Standart Getter- und Setter-Methoden zur Verfuegung gestellt.
 *
 * @author Marc Zoeller
 */
public class ApplicationConfig extends HttpServlet {

    private static Properties prop = new Properties();
    private static URL geneNameDownloadUrl;
    private static File geneNameFile;
    private static File propFile;
    private static int maxDbSize;
    private static int maxQuery;
    private static int urlTimeout;
    private static int queryTimeout;
    private static String dbName;
    private static String workingDir;
    private static String importProgress;
    private static String importDetails;

    /**
     * Diese Methode wird automatisch beim Erzeugen des Servlets aufgerufen. Sie
     * sollte nicht manuell aufgerufen werden. Es werden alle Einstellungen
     * geladen und initialisiert.
     */
    @Override
    public void init() {
        try {
            initProperties();

            geneNameDownloadUrl = new URL(prop.getProperty("geneNameDownloadUrl"));
            geneNameFile = new File(workingDir + prop.getProperty("geneNameFile"));
            if (!geneNameFile.exists())
                geneNameFile.createNewFile();
            HTTPStringDownloader.getInstance().HTTPtoFile(geneNameDownloadUrl, geneNameFile);
            urlTimeout = Integer.parseInt(prop.getProperty("urlTimeout"));
            dbName = prop.getProperty("dbName");
            queryTimeout = Integer.parseInt(prop.getProperty("queryTimeout"));
            maxDbSize = Integer.parseInt(prop.getProperty("maxDbSize"));
            maxQuery = Integer.parseInt(prop.getProperty("maxQuery"));
            SQLiteDB.initialize();
        } catch (Exception ex) {
            System.out.println("Error initializing properties: " + ex.getMessage());
            System.exit(1);
        }
    }

    /**
     * Diese Methode initialisiert das komplette Datei System. Es wird ein
     * Ordner 'MolDat' im aktuellen Benutzerverzeichnis erzeugt. Dort werden die
     * Dateien 'MolDat.properties', 'GeneNames.txt' und die Datenbank
     * gespeichert.
     */
    private static void initProperties() {
        workingDir = System.getProperty("user.dir") + "/MolDat/";
        File dir = new File(workingDir);
        propFile = new File(workingDir + "MolDat.properties");
        InputStream in = null;
        try {
            if (!propFile.exists()) {
                dir.mkdirs();
                propFile.createNewFile();
                in = moldat.ApplicationConfig.class.getClassLoader().getResourceAsStream("conf/" + "MolDat.properties");
                prop.load(in);
                prop.store(new FileOutputStream(propFile), "");
            }
            in = new FileInputStream(propFile);
            prop.load(in);
        } catch (IOException ex) {
            System.out.println("Failed to load properties: " + ex.getMessage());
            System.exit(1);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ex) {
                    System.out.println("Error initializing properties: " + ex.getMessage());
                    System.exit(1);
                }
            }
        }
    }

    /**
     * Liefert den Datenbank Namen.
     *
     * @return Name der Datenbank
     */
    public static String getDbName() {
        return dbName;
    }

    /**
     * Liefert den Pfad zur Datenbank.
     *
     * @return Pfad zur Datenbank
     */
    public static String getDbPath() {
        if (dbName.contains("/") || dbName.contains("\\"))
            return dbName;
        return getWorkingDir() + getDbName();
    }

    /**
     * Liefert die URL von der die Gen Synonyme runtergeladen werden können.
     *
     * @return Link zu Gen Synonymen
     */
    public static URL getGeneNameDownloadUrl() {
        return geneNameDownloadUrl;
    }

    /**
     * Liefert die Datei mit den Gen Synonymen.
     *
     * @return Datei mit Gen Synonymen
     */
    public static File getGeneNameFile() {
        return geneNameFile;
    }

    /**
     * Liefert den Import-Details. Der Wert wird fuer die 'Import-Page'
     * benoetigt.
     *
     * @return aktuelle Import-Details
     */
    public static String getImportDetails() {
        return importDetails;
    }

    /**
     * Liefert den Import-Fortschritt. Der Wert wird fuer die 'Import-Page'
     * benoetigt.
     *
     * @return aktueller Import-Fortschritt
     */
    public static String getImportProgress() {
        return importProgress;
    }

    /**
     * Liefer die maximal Groesse der Datenbank im RAM in Bytes.
     *
     * @return maximale Groesse der Datenbank
     */
    public static int getMaxDbSize() {
        return maxDbSize;
    }

    /**
     * Liefer die Anzahl der maximal gespeicherten Querys.
     *
     * @return Anzahl der max. gespeicherten Querys
     */
    public static int getMaxQuery() {
        return maxQuery;
    }

    /**
     * Liefert die Zeit bis zum Query-Timeout.
     *
     * @return Zeit bis zum Query-TImeout
     */
    public static int getQueryTimeout() {
        return queryTimeout;
    }

    /**
     * Liefert die Zeit bis zum URL-Timeout.
     *
     * @return Zeit bis zum URL-TImeout
     */
    public static int getUrlTimeout() {
        return urlTimeout;
    }

    /**
     * Liefer das aktuelle Arbeitsverzeichnis.
     *
     * @return aktuelle Arbeitesverzeichnis
     */
    public static String getWorkingDir() {
        return workingDir;
    }

    /**
     * Kopiert die Datenbank in den RAM. Dadurch wird die Benutzung stark
     * beschleunigt. Kann nur unter Linux Systemen benutzt werden.
     */
    protected static void mountDatabase() throws SQLException, ClassNotFoundException {
        if (!System.getProperty("os.name").equals("Linux"))
            throw new RuntimeException("This feature only works with Linux-OS.");
        File db = new File(getDbPath());
        if (db.length() > maxDbSize)
            throw new RuntimeException("Database is bigger than maximal size.");
        String[] content = dbName.split("/");
        dbName = "/dev/shm/" + content[content.length - 1];
        try {
            moveFile(db, new File(dbName));
        } catch (Exception ex) {
            throw new RuntimeException("Failed to move Database to RAM.");
        }
        SQLiteDB.initialize();
    }

    /**
     * Entfernt die Datenbank aus dem RAM und kopiert sie in das
     * Standardverzeichnis. Kann nur unter Linux Systemen benutzt werden.
     */
    protected static void unmountDatabase() {
        if (!dbName.contains("/dev/shm/"))
            return;
        File db = new File(dbName);
        String[] content = dbName.split("/");
        dbName = workingDir + content[content.length - 1];
        try {
            moveFile(db, new File(dbName));
        } catch (Exception ex) {
            throw new RuntimeException("Failed to remove Database from RAM."
                    + "Please remove the Database manually from " + db.getAbsolutePath()
                    + "to prevent a possible memory leak!");
        }
    }

    /*
     * Verschiebt die Datei src in die Datei target. Die Datei src dann geloescht.
     * Achtung: Falls target bereits existiert, wird die Datei ueberschrieben.
     */
    private static void moveFile(File src, File target) throws FileNotFoundException, IOException {
        InputStream in = null;
        OutputStream out = null;
        try {
            in = new FileInputStream(src);
            out = new FileOutputStream(target);
            byte[] buffer = new byte[1024];
            int length;
            while ((length = in.read(buffer)) > 0)
                out.write(buffer, 0, length);
        } finally {
            if (in != null)
                in.close();
            if (out != null)
                out.close();
        }
        src.delete();
    }

    /**
     * Setzt den Namen der Datenbank.
     *
     * @param newValue neuer Name der Datenbank
     */
    protected static void setDbName(String newValue) throws SQLException, ClassNotFoundException {
        dbName = newValue.replace("\\", "/");
        SQLiteDB.initialize();
        prop.setProperty("dbName", newValue);
    }

    /**
     * Setzt den Link, von dem die Gen Synonyme runtergeladen werden können.
     *
     * @param newValue Link von dem die Gen Synonyme runtergeladen werden
     * @throws MalformedURLException
     * @throws IOException
     */
    protected static void setGeneNameDownloadUrl(String newValue) throws MalformedURLException, IOException {
        if (geneNameDownloadUrl.equals(newValue))
            return;
        geneNameDownloadUrl = new URL(newValue);
        HTTPStringDownloader.getInstance().HTTPtoFile(geneNameDownloadUrl, geneNameFile);
        prop.setProperty("geneNamesDownloadUrl", newValue);
    }

    /**
     * Setzt den Namen der Datei, in der die Gen Synonyme gespeichert werden. In
     * dieser Datei werden die Synonme neu runtergeladen.
     *
     * @param newValue neue Datei, in der die Gen Synonyme gespeichert werden
     * @throws IOException
     */
    protected static void setGeneNameFile(File newValue) throws IOException {
        if (geneNameFile.equals(newValue))
            return;
        geneNameFile = newValue;
        if (!geneNameFile.exists())
            geneNameFile.createNewFile();
        HTTPStringDownloader.getInstance().HTTPtoFile(geneNameDownloadUrl, geneNameFile);
        GeneNameParser.loadSynonyms();
        prop.setProperty("geneNameFile", newValue.getAbsolutePath());
    }

    /**
     * Setzt die Import-Details. Dieser String wird zur Information auf der
     * 'Import-Page' benutzt.
     *
     * @param newValue neue Details
     */
    public static void setImportDetails(String newValue) {
        importDetails = newValue;
    }

    /**
     * Setzt den Import-Fortschritt. Dieser String wird zur Fortschrittsanzeige
     * auf der 'Import-Page' benutzt.
     *
     * @param newValue neuer Fortschritt
     */
    public static void setImportProgress(String newValue) {
        importProgress = newValue;
    }

    /**
     * Setzt die maximale Groesse der Datenbank im RAM.
     *
     * @param newValue neue maximale Groesse in Bytes
     */
    protected static void setMaxDbSize(Integer newValue) {
        maxDbSize = newValue;
        prop.setProperty("maxDbSize", newValue.toString());
    }

    /**
     * Setzt die Anzahl der Querys die gespeichert werden sollen.
     *
     * @param newValue neue Anzahl an Querys
     */
    protected static void setMaxQuery(Integer newValue) {
        maxQuery = newValue;
        prop.setProperty("maxQuery", newValue.toString());
    }

    /**
     * Setzt die Zeit bis zum URL-Timeout.
     *
     * @param newValue neue Zeit bis zum URL-Timeout
     */
    protected static void setUrlTimeout(Integer newValue) {
        urlTimeout = newValue;
        prop.setProperty("urlTimeout", newValue.toString());
    }

    /**
     * Setzt die Zeit bis zum Query-Timeout
     *
     * @param newValue neue Zeit bis zum Query-Timeout
     */
    protected static void setQueryTimeout(Integer newValue) {
        queryTimeout = newValue;
        prop.setProperty("queryTimeout", newValue.toString());
    }

    /**
     * Speichert alle Aenderungen an den Properties ab.
     *
     * @throws FileNotFoundException
     * @throws IOException
     */
    protected static void saveChanges() throws FileNotFoundException, IOException {
        prop.store(new FileOutputStream(propFile), "");
    }
}
